<?php

/**
 * Behavior for binding and translation management
 *
 * PHP versions 5
 * CAKEPHP versions 2.x
 * 
 * Green CMS - Content Management System and Framework Powerfull by Cakephp
 * Copyright 2012, GREEN GLOBAL CO., LTD (toancauxanh.vn)
 * 
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @author        Technology Lab No.I <tech1@toancauxanh.vn>
 * @link          
 * @package       Green.Model.Behavior
 * @since         Green v 1.0
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 */
App::uses('ModelBehavior', 'Model/Behavior');

class SerializableBehavior extends ModelBehavior {

    /**
     * Contains configuration settings for use with individual model objects.  This
     * is used because if multiple models use this Behavior, each will use the same
     * object instance.  Individual model settings should be stored as an
     * associative array, keyed off of the model name.
     *
     * @var array
     * @see Model::$alias
     */
    public $settings = array('priority' => 100);

    /**
     * Runtime configuration for this behavior
     *
     * @var array
     */
    public $runtime = array();

    /**
     * Initiate behavior for the model using specified settings.
     *
     * @param Model $Model Model using the behavior
     * @param array $settings Settings to override for model.
     * @return void
     */
    public function setup(Model $Model, $settings = array()) {
        // @todo extra options will be update in version 2
        $default = array();
        foreach ($Model->serializeFields as $field => $setting) {
            if (is_numeric($field)) {
                $field = $setting;
                $setting = array();
            }
            $this->settings[$Model->alias]['fields'][$field] = array_merge($default, (array) $setting);
        }
    }

    /**
     * beforeFind Callback
     * 
     * Runs before a find() operation. Used to auto detect serialized fields
     *
     * @param Model $Model	Model using the behavior
     * @param array $query Query parameters as set by cake
     * @return array
     */
    public function beforeFind(Model $Model, $query) {
        if (isset($query['fields'])) {
            $fields = array();
            foreach ((array) $query['fields'] as $field) {
                if (strpos($field, '.') !== false) {
                    $field = explode(".", $field);
                    $field = array_pop($field);
                }
                if (isset($this->settings[$Model->alias]['fields'][$field])) {
                    $fields[] = $field;
                }
            }
        } else {
            $fields = array_keys($this->settings[$Model->alias]['fields']);
        }
        $this->runtime[$Model->alias]['fields'] = $fields;
        return parent::beforeFind($Model, $query);
    }

    /**
     * beforeSave Callback
     * 
     * Runs before a save() operation. Used to auto serialized fields data
     *
     * @param Model $Model	Model using the behavior
     * @return array
     */
    public function beforeSave(Model $Model) {
        if (empty($this->settings[$Model->alias]['fields'])) {
            return true;
        }
        if (isset($Model->data[$Model->alias]) && Set::numeric(array_keys($Model->data[$Model->alias]))) {
            foreach ($Model->data[$Model->alias] as &$record) {
                $record = $this->_serializeData($Model, $record);
            }
        } elseif (Set::numeric(array_keys($Model->data))) {
            foreach ($Model->data as &$record) {
                $record = $this->_serializeData($Model, $record);
            }
        } else {
            $Model->data[$Model->alias] = $this->_serializeData($Model, $Model->data[$Model->alias]);
        }
        return true;
    }

    /**
     * afterFind Callback
     * 
     * Runs after a find() operation. Used to auto unserialized fields data
     *
     * @param Model $Model Model on which we are resetting
     * @param array $results Results of the find operation
     * @param boolean $primary true if this is the primary model that issued the find operation, false otherwise
     * @return $results
     */
    public function afterFind(Model $Model, $results, $primary) {
        if ($Model->findQueryType === 'count' || empty($this->runtime[$Model->alias]['fields'])) {
            return $results;
        }
        $_results = $results;
        foreach ($_results as &$data) {
            $data = $this->_unserializedData($Model, $data);
        }
        unset($this->runtime[$Model->alias]['fields']);
        return $_results;
    }

    /**
     * Used to auto serialized fields data
     *
     * @param string $className Model alias in $results
     * @param array $results data to filled
     * @param array $maps mappings has generate
     * @return array data serialized
     */
    protected function _serializeData(Model $Model, $data) {
        foreach ($data as $field => &$value) {
            if (!isset($this->settings[$Model->alias]['fields'][$field])) {
                continue;
            }
            $value = $this->serialize($Model, $value);
        }
        return $data;
    }

    /**
     * Used to unserialized fields data
     *
     * @param string $className Model alias in $results
     * @param array $results data to filled
     * @param array $maps mappings has generate
     * @return array data unserialized
     */
    protected function _unserializedData(Model $Model, $data) {
        foreach ($this->runtime[$Model->alias]['fields'] as $field) {
            if (isset($data[$Model->alias][$field]) && !empty($data[$Model->alias][$field])
                    && is_string($data[$Model->alias][$field])) {
                $data[$Model->alias][$field] = $this->unserialize($Model, $data[$Model->alias][$field]);
            }
        }
        return $data;
    }

    /**
     * Used to serialized data
     * 
     * @param Model $Model Model on which binding restriction is being applied
     * @param array $data data to serialize
     * @return string serialized
     */
    public function serialize(Model $Model, $data) {
        if (empty($data)) {
            return null;
        }
        return @serialize($data);
    }

    /**
     * Used to unserialized data
     *
     * @param Model $Model Model on which binding restriction is being applied
     * @param string $serialized
     * @return mixed The converted value is returned, and can be array, object or null on error.
     */
    public function unserialize(Model $Model, $serialized) {
        if (empty($serialized)) {
            return null;
        }
        return @unserialize((string) $serialized);
    }

}

?>